<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/objc_msgSend1/">
<meta property="og:title" content="       objc_msgSend消息传递学习笔记 - 对象方法消息传递流程 | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       objc_msgSend消息传递学习笔记 - 对象方法消息传递流程 | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/objc_msgSend1/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>objc_msgSend消息传递学习笔记 - 对象方法消息传递流程</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2016-08-06"> 2016-08-06
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <p>在<em>Effective Objective-C 2.0 - 52 Specific Ways to Improve Your iOS and OS X Programs</em>一书中，<em>tip 11</em>主要讲述了Objective-C中的消息传递机制。这也是Objective-C在C的基础上，做的最基础也是最重要的封装。</p>
<h2 id="static-binding-and-dynamic-binding">Static Binding And Dynamic Binding</h2>
<p>C中的函数调用方式，是使用的静态绑定(static binding)，即<strong>在编译期就能决定运行时所应调用的函数</strong>。而在Objective-C中，如果向某对象传递消息，就会使用动态绑定机制来决定需要调用的方法。而对于Objective-C的底层实现，都是C的函数。对象在收到消息之后，调用了哪些方法，完全取决于Runtime来决定，甚至可以在Runtime期间改变。</p>
<p>一般地，对对象发送消息，我们使用这种写法：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">id</span> <span class="n">returnValue</span> <span class="o">=</span> <span class="o">[</span><span class="no">DGObject</span> <span class="nb">test</span><span class="o">]</span><span class="p">;</span></code></pre></div>
<p>其中<code>someObject</code>为接收者(receiver)，<code>messageName</code>为选择子(selector)。当Compiler看的这条语句时，会将其转换成为一条标准的消息传递的C函数，<code>objc_msgSend</code>，形如：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">void</span> <span class="n">objc_msgSend</span><span class="p">(</span><span class="nb">id</span> <span class="nb">self</span><span class="p">,</span> <span class="no">SEL</span> <span class="n">cmd</span><span class="p">,</span> <span class="o">.</span><span class="n">.</span><span class="o">.</span><span class="p">)</span></code></pre></div>
<p>其中，<code>SEL</code>也就是之前对应的选择子，即为此文讨论的重点。我们对应的写出之前代码在Compiler处理后的C语句：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">id</span> <span class="n">returnValue</span> <span class="o">=</span> <span class="n">objc_msgSend</span><span class="p">(</span><span class="no">DGObject</span><span class="p">,</span> <span class="vi">@selector</span><span class="p">(</span><span class="nb">test</span><span class="p">));</span></code></pre></div>
<h2 id="selector">@selector()</h2>
<p>对于<code>SEL</code>类型，也就是我们经常使用的<code>@selector()</code>，在很多的书籍资料中的定义是这样：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">typedef</span> <span class="n">struct</span> <span class="n">objc_selector</span> <span class="o">*</span><span class="no">SEL</span><span class="p">;</span></code></pre></div>
<p>而至于这个<code>objc_selector</code>的结构体是如何定义的，这就要取决于我们Runtime框架的类型，在iOS开发中，我们使用的是Apple的<code>&lt;objc/runtime.h&gt;</code>(GNU也有Runtime的framework)。在OS X中<code>SEL</code>被映射成为一个C字符串(char[])，这个字符串也就是方法名。</p>
<p>我们在lldb中，进行测试：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14704804939083.jpg" alt="" /></p>
<p>(图释：<code>test</code>是在<code>DGObject</code>Class中已经定义的方法名，而<code>not_define_test</code>和<code>not_define_test_2</code>没有定义)</p>
<p>第一行我们验证了<code>@selector</code>是一个char[]类型。其他的结果我们可以总结出：<code>@selector()</code>选择子<strong>只与函数名有关</strong>。而且还有一个规律，那就是倘若选择子方法已经在编译期由Compiler进行静态绑定，则其存储的地址就会更加的具体。</p>
<p>发送消息所依托的选择子只与函数名有关，我们便可以猜想到为什么Objective-C中没有像C++、C#那样的<strong><a href="https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD">函数重载</a></strong>特性，因为<strong>选择子并不由参数和函数名共同决定</strong>。</p>
<p>那么为什么要有这个选择子呢？在<em><a href="http://draveness.me/message/">从源代码看 ObjC 中消息的发送</a></em>一文中，作者<em>Draveness</em>对其原因进行了推断：</p>
<blockquote>
  <ol>
    <li>Objective-C 为我们维护了一个巨大的选择子表</li>
    <li>在使用 <code>@selector()</code> 时会从这个选择子表中根据选择子的名字查找对应的 <code>SEL</code>。如果没有找到，则会生成一个 <code>SEL</code> 并添加到表中</li>
    <li>在编译期间会扫描全部的头文件和实现文件将其中的方法以及使用 <code>@selector()</code> 生成的选择子加入到选择子表中</li>
  </ol>
</blockquote>
<h2 id="objcmsgsend">objc_msgSend</h2>
<p>在选择子拿到对应的地址后，<code>objc_msgSend</code>会依据接收者与选择子的类型来调用适当方法。为了学习此过程，我从<a href="git@github.com:RetVal/objc-runtime.git">opensource.apple.com的git仓库中</a>clone了Runtime源码，并在<code>x86_64</code>架构下macOS环境进行运行。</p>
<p>另外，我在整个工程中增加了一个Class:</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="no">DGObject</span><span class="o">.</span><span class="n">h</span>
<span class="vi">@interface</span> <span class="no">DGObject</span> <span class="p">:</span> <span class="no">NSObject</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="nb">test</span><span class="p">;</span>
<span class="vi">@end</span>
<span class="sr">//</span> <span class="no">DGObject</span><span class="o">.</span><span class="n">m</span>
<span class="c1">#import &quot;DGObject.h&quot;</span>
<span class="vi">@implementation</span> <span class="no">DGObject</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="nb">test</span> <span class="p">{</span>
    <span class="nb">printf</span><span class="p">(</span><span class="s2">&quot;Hello World. &quot;</span><span class="p">);</span>
<span class="p">}</span>
<span class="vi">@end</span></code></pre></div>
<p>并在main入口函数中进行改动：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">int</span> <span class="n">main</span><span class="p">(</span><span class="n">int</span> <span class="n">argc</span><span class="p">,</span> <span class="n">const</span> <span class="n">char</span> <span class="o">*</span> <span class="n">argv</span><span class="o">[]</span><span class="p">)</span> <span class="p">{</span>
    <span class="vi">@autoreleasepool</span> <span class="p">{</span>
        <span class="no">DGObject</span> <span class="o">*</span><span class="n">obj</span> <span class="o">=</span> <span class="o">[[</span><span class="no">DGObject</span> <span class="n">alloc</span><span class="o">]</span><span class="n">init</span><span class="o">]</span><span class="p">;</span>
        <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%p&quot;</span><span class="p">,</span> <span class="vi">@selector</span><span class="p">(</span><span class="nb">test</span><span class="p">));</span>
        <span class="o">[</span><span class="n">obj</span> <span class="nb">test</span><span class="o">]</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>然后我们在<code>objc-runtime-new.mm</code>中，进行debug。为了研究清楚Runtime是如何查询到调用函数，我们在<code>lookUpImpOrForward</code>下断点。当程序执行<code>[obj test]</code>后，我们发现到达断点位置，并且观察此时的调用栈情况：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14704829334247.jpg" alt="" /></p>
<p><code>objc_msgSend</code>并不是直接调用查询方法，而是先调用了<code>_class_lookupMethodAndLoadCache3</code>这个函数。看下它的源码：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">IMP</span> <span class="n">_class_lookupMethodAndLoadCache3</span><span class="p">(</span><span class="nb">id</span> <span class="n">obj</span><span class="p">,</span> <span class="no">SEL</span> <span class="n">sel</span><span class="p">,</span> <span class="no">Class</span> <span class="n">cls</span><span class="p">){</span>
    <span class="k">return</span> <span class="n">lookUpImpOrForward</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> 
                              <span class="no">YES</span><span class="o">/*</span><span class="kp">initialize</span><span class="o">*/</span><span class="p">,</span> <span class="no">NO</span><span class="o">/*</span><span class="n">cache</span><span class="o">*/</span><span class="p">,</span> <span class="no">YES</span><span class="o">/*</span><span class="n">resolver</span><span class="o">*/</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<p><code>_class_lookupMethodAndLoadCache3</code>就好像一个中转函数，并给出了在查询IMP指针前默认参量的几个布尔值。而由于我们的方法没有进行方法转发，则直接调用了<code>_class_lookupMethodAndLoadCache3</code>这个函数。而当对象在收到无法解读的消息之后，即启动消息转发机制，这时候应该会进入<code>lookUpImpOrNil</code>这个方法。这也是objc_msgSend的一种优化方式。</p>
<p>这里还要注意一点，就是关于Cache的默认参数是<code>NO</code>，因为在objc_msgSend中已经进行过缓存查询。以下是objc_msgSend的汇编实现：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">ENTRY</span>	<span class="n">_objc_msgSend</span>
	<span class="no">MESSENGER_START</span>
	<span class="sr">//</span> <span class="ss">NilTest</span><span class="p">:</span><span class="err">宏，判断被发送消息的对象是否为</span><span class="kp">nil</span><span class="err">。</span>
	<span class="sr">//</span> <span class="err">如果为</span><span class="kp">nil</span><span class="err">直接返回。</span>
	<span class="no">NilTest</span>	<span class="no">NORMAL</span>
	<span class="sr">//</span> <span class="no">GetIsaFast</span><span class="err">快速获取</span><span class="n">isa</span><span class="err">指针地址，并放入</span><span class="n">r11</span><span class="err">寄存器</span>
	<span class="no">GetIsaFast</span> <span class="no">NORMAL</span>		<span class="sr">//</span> <span class="n">r11</span> <span class="o">=</span> <span class="nb">self</span><span class="o">-&gt;</span><span class="n">isa</span>
	<span class="sr">//</span> <span class="err">查找类缓存中</span><span class="n">selector</span><span class="err">的</span><span class="no">IMP</span><span class="err">指针，并放到</span><span class="n">r10</span><span class="err">寄存器</span>
	<span class="sr">//</span> <span class="err">如果不存在，则在</span><span class="n">class</span><span class="err">的方法</span><span class="n">list</span><span class="err">中查询</span>
	<span class="no">CacheLookup</span> <span class="no">NORMAL</span>		<span class="sr">//</span> <span class="n">calls</span> <span class="no">IMP</span> <span class="n">on</span> <span class="n">success</span>
	<span class="sr">//</span> <span class="no">NilTest</span><span class="err">的许可量以及</span><span class="no">GetIsaFast</span><span class="err">的许可量</span>
	<span class="no">NilTestSupport</span>	<span class="no">NORMAL</span>
	<span class="no">GetIsaSupport</span>	<span class="no">NORMAL</span>
<span class="sr">//</span> <span class="n">cache</span> <span class="ss">miss</span><span class="p">:</span> <span class="n">go</span> <span class="n">search</span> <span class="n">the</span> <span class="nb">method</span> <span class="n">lists</span>
<span class="ss">LCacheMiss</span><span class="p">:</span>
	<span class="sr">//</span> <span class="n">isa</span> <span class="n">still</span> <span class="k">in</span> <span class="n">r11</span>
	<span class="sr">//</span> <span class="no">MethodTableLoopup</span><span class="err">这个宏是</span><span class="n">__class_lookupMethodAndLoadCache3</span><span class="err">函数的入口</span>
	<span class="sr">//</span> <span class="err">调用条件是在缓存中没有查询到方法对应</span><span class="no">IMP</span>
	<span class="no">MethodTableLookup</span> <span class="o">%</span><span class="n">a1</span><span class="p">,</span> <span class="o">%</span><span class="n">a2</span>	<span class="sr">//</span> <span class="n">r11</span> <span class="o">=</span> <span class="no">IMP</span>
	<span class="n">cmp</span>	<span class="o">%</span><span class="n">r11</span><span class="p">,</span> <span class="o">%</span><span class="n">r11</span>		<span class="sr">//</span> <span class="n">set</span> <span class="n">eq</span> <span class="p">(</span><span class="n">nonstret</span><span class="p">)</span> <span class="k">for</span> <span class="n">forwarding</span>
	<span class="n">jmp</span>	<span class="o">*%</span><span class="n">r11</span>			<span class="sr">//</span> <span class="n">goto</span> <span class="o">*</span><span class="n">imp</span>
	<span class="no">END_ENTRY</span>	<span class="n">_objc_msgSend</span></code></pre></div>
<p>趁热打铁，再来看一下<em>MethodTableLoopup</em>这个宏的实现：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">.</span><span class="n">macro</span> <span class="no">MethodTableLookup</span>
	<span class="no">MESSENGER_END_SLOW</span>
	<span class="no">SaveRegisters</span>
	<span class="sr">//</span> <span class="n">_class_lookupMethodAndLoadCache3</span><span class="p">(</span><span class="n">receiver</span><span class="p">,</span> <span class="n">selector</span><span class="p">,</span> <span class="n">class</span><span class="p">)</span>
	<span class="sr">//</span> <span class="err">从</span><span class="n">a1</span><span class="p">,</span> <span class="n">a2</span><span class="p">,</span> <span class="n">a3</span><span class="err">中分别拿到对应参数</span>
	<span class="n">movq</span>	<span class="vg">$0</span><span class="p">,</span> <span class="o">%</span><span class="n">a1</span>
	<span class="n">movq</span>	<span class="vg">$1</span><span class="p">,</span> <span class="o">%</span><span class="n">a2</span>
	<span class="n">movq</span>	<span class="o">%</span><span class="n">r11</span><span class="p">,</span> <span class="o">%</span><span class="n">a3</span>
	<span class="sr">//</span> <span class="err">调用</span><span class="n">__class_lookupMethodAndLoadCache3</span>
	<span class="n">call</span>	<span class="n">__class_lookupMethodAndLoadCache3</span>
	<span class="sr">//</span> <span class="no">IMP</span> <span class="n">is</span> <span class="n">now</span> <span class="k">in</span> <span class="o">%</span><span class="n">rax</span>
	<span class="sr">//</span> <span class="err">将</span><span class="no">IMP</span><span class="err">从</span><span class="n">r11</span><span class="err">挪至</span><span class="n">rax</span>
	<span class="n">movq</span>	<span class="o">%</span><span class="n">rax</span><span class="p">,</span> <span class="o">%</span><span class="n">r11</span>
	<span class="no">RestoreRegisters</span>
<span class="o">.</span><span class="n">endmacro</span></code></pre></div>
<p>而在<code>objc-msg-x86_64.s</code>中有多个以objc_msgSend为前缀的方法，这个是根据返回值类型和调用者类型分别处理的，我列举三个常用的</p>
<table>
  <thead>
    <tr>
      <th>objc_msgSend_stret</th>
      <th>待发送的消息要返回结构体<br />前提是只有当CPU的寄存器能够容纳的下消息返回类型。</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>objc_msgSend_fpret</td>
      <td>消息返回的是浮点数。<br />因为某些架构的CPU调用函数，需要对浮点数寄存器做特殊处理。</td>
    </tr>
    <tr>
      <td>objc_msgSendSuper</td>
      <td>需要向superClass发送消息时调用。</td>
    </tr>
  </tbody>
</table>
<h2 id="lookupimporforward">lookUpImpOrForward</h2>
<p>之后我们随着调用栈往上看，在接受到消息入口的命令后，Runtime要开始进行查找方法的操作，源码如下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">IMP</span> <span class="n">lookUpImpOrForward</span><span class="p">(</span><span class="no">Class</span> <span class="n">cls</span><span class="p">,</span> <span class="no">SEL</span> <span class="n">sel</span><span class="p">,</span> <span class="nb">id</span> <span class="n">inst</span><span class="p">,</span> 
                       <span class="n">bool</span> <span class="kp">initialize</span><span class="p">,</span> <span class="n">bool</span> <span class="n">cache</span><span class="p">,</span> <span class="n">bool</span> <span class="n">resolver</span><span class="p">)</span> <span class="p">{</span>
    <span class="no">Class</span> <span class="n">curClass</span><span class="p">;</span>
    <span class="no">IMP</span> <span class="n">imp</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">;</span>
    <span class="no">Method</span> <span class="n">meth</span><span class="p">;</span>
    <span class="n">bool</span> <span class="n">triedResolver</span> <span class="o">=</span> <span class="no">NO</span><span class="p">;</span>
    <span class="n">runtimeLock</span><span class="o">.</span><span class="n">assertUnlocked</span><span class="p">();</span>
    <span class="sr">//</span> <span class="err">检查是否添加缓存锁，如果没有进行缓存查询。</span>
    <span class="sr">//</span> <span class="err">查到便返回</span><span class="no">IMP</span><span class="err">指针</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">cache</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">imp</span> <span class="o">=</span> <span class="n">cache_getImp</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">imp</span><span class="p">)</span> <span class="k">return</span> <span class="n">imp</span><span class="p">;</span>
    <span class="p">}</span>
	<span class="sr">//</span> <span class="err">通过调用</span><span class="n">realizeClass</span><span class="err">方法，分配可读写</span><span class="sb">`class_rw_t`</span><span class="err">的空间</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">cls</span><span class="o">-&gt;</span><span class="n">isRealized</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">rwlock_writer_t</span> <span class="n">lock</span><span class="p">(</span><span class="n">runtimeLock</span><span class="p">);</span>
        <span class="n">realizeClass</span><span class="p">(</span><span class="n">cls</span><span class="p">);</span>
    <span class="p">}</span>
	<span class="sr">//</span> <span class="err">倘若未进行初始化，则初始化</span>
    <span class="k">if</span> <span class="p">(</span><span class="kp">initialize</span>  <span class="o">&amp;&amp;</span>  <span class="o">!</span><span class="n">cls</span><span class="o">-&gt;</span><span class="n">isInitialized</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">_class_initialize</span> <span class="p">(</span><span class="n">_class_getNonMetaClass</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">inst</span><span class="p">));</span>
    <span class="p">}</span>
	<span class="sr">//</span> <span class="err">保证方法查询，并进行缓存填充</span><span class="p">(</span><span class="n">cache</span><span class="o">-</span><span class="n">fill</span><span class="p">)</span>
  	<span class="k">retry</span><span class="p">:</span>
    <span class="n">runtimeLock</span><span class="o">.</span><span class="n">read</span><span class="p">();</span>
    <span class="sr">//</span> <span class="err">是否忽略</span><span class="no">GC</span><span class="err">垃圾回收机制（仅用在</span><span class="n">macOS</span><span class="err">中）</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ignoreSelector</span><span class="p">(</span><span class="n">sel</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">imp</span> <span class="o">=</span> <span class="n">_objc_ignored_method</span><span class="p">;</span>
        <span class="n">cache_fill</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">imp</span><span class="p">,</span> <span class="n">inst</span><span class="p">);</span>
        <span class="n">goto</span> <span class="n">done</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">当前类的缓存列表中进行查找</span>
    <span class="n">imp</span> <span class="o">=</span> <span class="n">cache_getImp</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">imp</span><span class="p">)</span> <span class="n">goto</span> <span class="n">done</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">从类的方法列表中进行查询</span>
    <span class="n">meth</span> <span class="o">=</span> <span class="n">getMethodNoSuper_nolock</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">meth</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">log_and_fill_cache</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">meth</span><span class="o">-&gt;</span><span class="n">imp</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">inst</span><span class="p">,</span> <span class="n">cls</span><span class="p">);</span>
        <span class="n">imp</span> <span class="o">=</span> <span class="n">meth</span><span class="o">-&gt;</span><span class="n">imp</span><span class="p">;</span>
        <span class="n">goto</span> <span class="n">done</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">从父类中循环遍历</span>
    <span class="n">curClass</span> <span class="o">=</span> <span class="n">cls</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">((</span><span class="n">curClass</span> <span class="o">=</span> <span class="n">curClass</span><span class="o">-&gt;</span><span class="n">superclass</span><span class="p">))</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">父类的缓存列表中查询</span>
        <span class="n">imp</span> <span class="o">=</span> <span class="n">cache_getImp</span><span class="p">(</span><span class="n">curClass</span><span class="p">,</span> <span class="n">sel</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">imp</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">imp</span> <span class="o">!=</span> <span class="p">(</span><span class="no">IMP</span><span class="p">)</span><span class="n">_objc_msgForward_impcache</span><span class="p">)</span> <span class="p">{</span>
                <span class="sr">//</span> <span class="err">如果在父类中发现方法，则填充到该类缓存列表</span>
                <span class="n">log_and_fill_cache</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">imp</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">inst</span><span class="p">,</span> <span class="n">curClass</span><span class="p">);</span>
                <span class="n">goto</span> <span class="n">done</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="k">else</span> <span class="p">{</span>
                <span class="k">break</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">从父类的方法列表中查询</span>
        <span class="n">meth</span> <span class="o">=</span> <span class="n">getMethodNoSuper_nolock</span><span class="p">(</span><span class="n">curClass</span><span class="p">,</span> <span class="n">sel</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">meth</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">log_and_fill_cache</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">meth</span><span class="o">-&gt;</span><span class="n">imp</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">inst</span><span class="p">,</span> <span class="n">curClass</span><span class="p">);</span>
            <span class="n">imp</span> <span class="o">=</span> <span class="n">meth</span><span class="o">-&gt;</span><span class="n">imp</span><span class="p">;</span>
            <span class="n">goto</span> <span class="n">done</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">进入</span><span class="nb">method</span> <span class="n">resolve</span><span class="err">过程</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">resolver</span>  <span class="o">&amp;&amp;</span>  <span class="o">!</span><span class="n">triedResolver</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">runtimeLock</span><span class="o">.</span><span class="n">unlockRead</span><span class="p">();</span>
        <span class="sr">//</span> <span class="err">调用</span><span class="n">_class_resolveMethod</span><span class="err">，解析没有实现的方法</span>
        <span class="n">_class_resolveMethod</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">inst</span><span class="p">);</span>
        <span class="sr">//</span> <span class="err">进行二次尝试</span>
        <span class="n">triedResolver</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
        <span class="n">goto</span> <span class="k">retry</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">没有找到方法，启动消息转发</span>
    <span class="n">imp</span> <span class="o">=</span> <span class="p">(</span><span class="no">IMP</span><span class="p">)</span><span class="n">_objc_msgForward_impcache</span><span class="p">;</span>
    <span class="n">cache_fill</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">imp</span><span class="p">,</span> <span class="n">inst</span><span class="p">);</span>
 <span class="ss">done</span><span class="p">:</span>
    <span class="n">runtimeLock</span><span class="o">.</span><span class="n">unlockRead</span><span class="p">();</span>
    <span class="k">return</span> <span class="n">imp</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>以上就是整个的查找方法流程，然后我们再对其中的一些方法逐一解读。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">static</span> <span class="n">method_t</span> <span class="o">*</span><span class="n">getMethodNoSuper_nolock</span><span class="p">(</span><span class="no">Class</span> <span class="n">cls</span><span class="p">,</span> <span class="no">SEL</span> <span class="n">sel</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">runtimeLock</span><span class="o">.</span><span class="n">assertLocked</span><span class="p">();</span>
    <span class="sr">//</span> <span class="err">遍历所在类的</span><span class="nb">methods</span><span class="err">，这里的</span><span class="nb">methods</span><span class="err">是</span><span class="no">List</span><span class="err">链式类型，里面存放的都是指针</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">auto</span> <span class="n">mlists</span> <span class="o">=</span> <span class="n">cls</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">()</span><span class="o">-&gt;</span><span class="nb">methods</span><span class="o">.</span><span class="n">beginLists</span><span class="p">(),</span> <span class="k">end</span> <span class="o">=</span> <span class="n">cls</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">()</span><span class="o">-&gt;</span><span class="nb">methods</span><span class="o">.</span><span class="n">endLists</span><span class="p">();</span> <span class="n">mlists</span> <span class="o">!=</span> <span class="k">end</span><span class="p">;</span> <span class="o">++</span><span class="n">mlists</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">method_t</span> <span class="o">*</span><span class="n">m</span> <span class="o">=</span> <span class="n">search_method_list</span><span class="p">(</span><span class="o">*</span><span class="n">mlists</span><span class="p">,</span> <span class="n">sel</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">m</span><span class="p">)</span> <span class="k">return</span> <span class="n">m</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="kp">nil</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>这里的对于 class 存储方式，我在以后的博文中会分析其存储结构。</p>
<p>而对于没有实现方法的解析过程中，会有以下过程：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">void</span> <span class="n">_class_resolveMethod</span><span class="p">(</span><span class="no">Class</span> <span class="n">cls</span><span class="p">,</span> <span class="no">SEL</span> <span class="n">sel</span><span class="p">,</span> <span class="nb">id</span> <span class="n">inst</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="n">cls</span><span class="o">-&gt;</span><span class="n">isMetaClass</span><span class="p">())</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="n">try</span> <span class="o">[</span><span class="n">cls</span> <span class="ss">resolveInstanceMethod</span><span class="p">:</span><span class="n">sel</span><span class="o">]</span>
        <span class="sr">//</span> <span class="err">针对于对象方法的操作</span>
        <span class="sr">//</span> <span class="err">这个方法是动态方法解析中，当收到无法解读的消息后调用。</span>
        <span class="sr">//</span> <span class="err">这个方法也会用在</span><span class="vi">@dynamic</span><span class="err">，以后会在消息转发机制的源码分析中介绍</span>
        <span class="n">_class_resolveInstanceMethod</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">inst</span><span class="p">);</span>
    <span class="p">}</span> 
    <span class="k">else</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="n">try</span> <span class="o">[</span><span class="n">nonMetaClass</span> <span class="ss">resolveClassMethod</span><span class="p">:</span><span class="n">sel</span><span class="o">]</span>
        <span class="sr">//</span> <span class="ow">and</span> <span class="o">[</span><span class="n">cls</span> <span class="ss">resolveInstanceMethod</span><span class="p">:</span><span class="n">sel</span><span class="o">]</span>
        <span class="sr">//</span> <span class="err">针对于类方法的操作，说明同上</span>
        <span class="n">_class_resolveClassMethod</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">inst</span><span class="p">);</span>
        <span class="sr">//</span> <span class="err">再次启动查询，并且判断是否拥有缓存中消息标记</span><span class="n">_objc_msgForward_impcache</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">lookUpImpOrNil</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">inst</span><span class="p">,</span> 
                            <span class="no">NO</span><span class="o">/*</span><span class="kp">initialize</span><span class="o">*/</span><span class="p">,</span> <span class="no">YES</span><span class="o">/*</span><span class="n">cache</span><span class="o">*/</span><span class="p">,</span> <span class="no">NO</span><span class="o">/*</span><span class="n">resolver</span><span class="o">*/</span><span class="p">))</span> <span class="p">{</span>
            <span class="sr">//</span> <span class="err">说明可能不是</span> <span class="n">metaclass</span> <span class="err">的方法实现，当做对象方法尝试</span>
            <span class="n">_class_resolveInstanceMethod</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">sel</span><span class="p">,</span> <span class="n">inst</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>来单步调试一下程序，由于我们的test方法属于正常的类方法，所以会进入正常地查询类方法列表中查到，进入done函数块，返回到objc_msgSend方法，最终会到我们的函数调用位置：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14705310410822.jpg" alt="" /></p>
<h2 id="imp-in-method-list-flow">IMP in Method List Flow</h2>
<p>来简单总结一下在第一次调用某个对象方法的消息传递流程：当代码执行到某个对象（第一次）调用某个方法后，首先会确定这个方法的接收者和选择子，并组装成C的objc_msgSend函数形式，启动消息传递机制。</p>
<p>objc_msgSend函数是使用汇编语言实现的，其中我们先尝试的从缓存表中（也就是常说的快速映射表）查询缓存，倘若查询失败，则会将具体的类对象、选择子、接收者在指定的内存单元中存储，并调用<code>__class_lookupMethodAndLoadCache3</code>函数。<code>__class_lookupMethodAndLoadCache3</code>我们俗称为<strong>在方法列表中查询的入口函数</strong>，他会直接调用<code>lookUpImpOrForward</code>进行查询方法对应的IMP指针。由于我们是方法函数，在获取方法列表后，即可查询到IMP指针。由于是第一次调用，则会把我们的方法加入缓存，并goto到done代码块，返回IMP指针。当objc_msgSend接收到IMP指针后存储至<code>rax</code>寄存器，返回调用函数位置，完成整个消息传递流程。</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14705330688513.jpg" alt="" /></p>
<h2 id="section">写在最后</h2>
<p>其实消息传递及转发流程是一个相对来说比较复杂的机制。本文所讲述的流程是我们最常见的一种形式。在之后的消息传递与转发的博文中，还会更加深入的探讨这一机制相关流程并深入的阅读源码。</p>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《objc_msgSend消息传递学习笔记 - 对象方法消息传递流程》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/objc_msgSend1/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/objc_msgSend1/&amp;title=《objc_msgSend消息传递学习笔记 - 对象方法消息传递流程》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/objc_msgSend1/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/objc_msgSend1/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>